package soundscovery;

import ecologylab.xml.ElementState;
import ecologylab.xml.TranslationSpace;
import jAudioFeatureExtractor.Controller;
import jAudioFeatureExtractor.DataModel;
import jAudioFeatureExtractor.ModelListener;
import jAudioFeatureExtractor.ACE.DataTypes.Batch;
import jAudioFeatureExtractor.ACE.DataTypes.FeatureDefinition;
import jAudioFeatureExtractor.Aggregators.Aggregator;
import jAudioFeatureExtractor.Aggregators.AggregatorContainer;
import jAudioFeatureExtractor.AudioFeatures.FeatureExtractor;
import jAudioFeatureExtractor.DataTypes.RecordingInfo;
import javax.sound.*;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Scanner;
import java.util.Stack;
import kohonen.LearningData;
import kohonen.WTALearningFunction;
import metrics.EuclidesMetric;
import network.DefaultNetwork;
import topology.MatrixTopology;
import java.util.Vector;
import javax.swing.JFileChooser;
import javax.sound.*;
import learningFactorFunctional.GaussFunctionalFactor;
import metrics.MinkowskiMetric;
import org.w3c.dom.NodeList;

//TODO fix wav playing
//TODO create test framework
//TODO create way to save some structures

//TODO make SOM of SOMS possible

public class Launch {
	public static String folderOfWavFiles = "";
	static AllSounds allSounds;
	static Vector<Vector<Sound>> clusters = new Stack<Vector<Sound>>();
	static int mfccIndex = 0;

	static int width = 20;
	static int height = 20;
	static int runs = 4000;
	static int mfccCount = 5;

	static String fdout = "output.xmlFD.xml";
	static String fvout = "output.xmlFV.xml";

	static String workingDirectory = "";
	static AePlayWav player;

	public static void playSound(String nameWithoutWavExtension) {
		System.out.println("Playing " + folderOfWavFiles
				+ nameWithoutWavExtension + ".wav");
		// if(player != null){
		// player.stopPlaying();
		// }
		player = new AePlayWav(folderOfWavFiles + nameWithoutWavExtension
				+ ".wav");// .start();
		player.start();
	}

	// addSave Vector of sound files to folder
	public static void saveSounds(Vector<String> toSaveNoExtensions) {
		String folderToSaveTo = "";
		System.out.println("Trying to choose a folder");
		JFileChooser chooser = new JFileChooser();
		chooser.setCurrentDirectory(new java.io.File("."));
		chooser.setDialogTitle("Select a Folder with Wav Files");
		chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
		//
		// disable the "All files" option.
		//
		chooser.setAcceptAllFileFilterUsed(false);
		//
		if (chooser.showOpenDialog(null) == JFileChooser.OPEN_DIALOG) {
			System.out.println("getCurrentDirectory(): "
					+ chooser.getCurrentDirectory());
			System.out.println("getSelectedFile() : "
					+ chooser.getSelectedFile());
			folderToSaveTo = chooser.getSelectedFile().toString() + "\\";
		} else {
			System.out.println("No Selection ");
		}

		for (int i = 0; i < toSaveNoExtensions.size(); i++) {
			String fromfile = folderOfWavFiles + toSaveNoExtensions.get(i)
					+ ".wav";
			String tofile = folderToSaveTo + toSaveNoExtensions.get(i) + ".wav";
			System.out.println("Copying\n" + fromfile + "\n to \n" + tofile);
			CopyFile.copyfile(fromfile, tofile);
		}
	}

	public static void chooseFolder() {
		System.out.println("Trying to choose a folder");
		JFileChooser chooser = new JFileChooser();
		chooser.setCurrentDirectory(new java.io.File("."));
		chooser.setDialogTitle("Select a Folder with Wav Files");
		chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
		//
		// disable the "All files" option.
		//
		chooser.setAcceptAllFileFilterUsed(false);
		//
		if (chooser.showOpenDialog(null) == JFileChooser.OPEN_DIALOG) {
			System.out.println("getCurrentDirectory(): "
					+ chooser.getCurrentDirectory());
			System.out.println("getSelectedFile() : "
					+ chooser.getSelectedFile());
			folderOfWavFiles = chooser.getSelectedFile().toString() + "\\";
		} else {
			System.out.println("No Selection ");
		}
	}

	public static void executeCommand(String[] files) {

		try {
			Runtime rt = Runtime.getRuntime();
			String[] argsp1 = { "cmd.exe", "/E:1900", "/C", "java", "-jar",
					"jAudio.jar", "-s", "settings.xml", "output.xml" };
			String args[] = new String[argsp1.length + files.length];
			for (int i = 0; i < argsp1.length; i++) {
				args[i] = argsp1[i];
			}
			for (int i = 0; i < files.length; i++) {
				args[i + argsp1.length] = folderOfWavFiles + files[i];
				// System.out.println("Argument: "+folderOfWavFiles+files[i]);
			}

			Process proc = rt.exec(args);
			InputStream stdin = proc.getInputStream();
			InputStreamReader isr = new InputStreamReader(stdin);
			BufferedReader br = new BufferedReader(isr);
			String line = null;
			// System.out.println("<OUTPUT>");

			while ((line = br.readLine()) != null) {
				System.out.println(line);
			}
			// System.out.println("</OUTPUT>");
			int exitVal = proc.waitFor();
			// System.out.println("Process exitValue: " + exitVal);
		} catch (Throwable t) {
			t.printStackTrace();
		}
	}

	public static void pwd() {
		// MFCC cool = new MFCC();
		java.io.File f = new java.io.File(".");
		try {
			workingDirectory = f.getCanonicalPath();
			System.out.println("PWD: " + workingDirectory);

		} catch (Exception e) {
		}
	}

	public static void extractFeatures() {
		Batch b = new Batch();

		String[] fileLists = allSounds.getSoundStrings();
		RecordingInfo[] recording_info = new RecordingInfo[fileLists.length];

		String fFile = workingDirectory + "\\features.xml";
		DataModel dm = new DataModel(fFile, null);
		FeatureDefinition[] definitions = dm.featureDefinitions;
		FeatureExtractor[] fe = dm.features;
		String[] feAttributes = new String[] {};
		HashMap<String, Boolean> active = new HashMap<String, Boolean>();
		HashMap<String, String[]> attribute = new HashMap<String, String[]>();

		// populate the hashmap with a default of everything off
		for (int i = 0; i < dm.defaults.length; i++) {
			Boolean defa = new Boolean(false);
			active.put(definitions[i].name, defa);
			feAttributes = fe[i].getFeatureDefinition().attributes;
			if (feAttributes.length > 0) {
				String[] feAttributeValues = new String[feAttributes.length];
				for (int j = 0; j < feAttributes.length; j++) {
					try {
						feAttributeValues[j] = fe[i].getElement(j);
					} catch (Exception e) {
						System.out.println("ERROR retrieving attribute value "
								+ " for " + definitions[i].name);
						feAttributeValues[j] = "";
					}
				}
				attribute.put(definitions[i].name, feAttributeValues);
			} else {
				String[] feAttributeValues = new String[] {};
				attribute.put(definitions[i].name, feAttributeValues);
			}
		}

		// Set AGGREGATORS
		String[] aggNames = { "Standard Deviation", "Mean" };
		String[][] aggFeatures = { {}, {} };
		String[][] aggParameters = { {}, {} };
		OutputStream destinationFK = null;
		OutputStream destinationFV = null;
		try {
			destinationFK = new FileOutputStream(new File(fdout));
			destinationFV = new FileOutputStream(new File(fvout));
		} catch (Exception e) {
		}
		System.out
				.println("Adding Extraction Options and Parameters to jAudio Feature Extraction Batch");
		dm.featureKey = destinationFK;
		dm.featureValue = destinationFV;

		active.put("MFCC", true);
		attribute.put("MFCC", new String[] { "" + mfccCount });

		b.setDataModel(dm);
		b.setWindowSize(512);
		b.setWindowOverlap(0);
		b.setSamplingRate(16000);
		b.setNormalise(false);
		b.setPerWindow(false);
		b.setOverall(true);

		try {
			b.setRecordings(listOfFiles);
		} catch (Exception e1) {
			e1.printStackTrace();
		}
		b.setOutputType(0);// ACE is a 0
		b.setFeatures(active, attribute);
		b.setAggregators(aggNames, aggFeatures, aggParameters);

		try {
			b.execute();
		} catch (Exception e) {
			e.printStackTrace();
		}
		System.out.println("Outputting jAudio Batch job for execution");
	}


	public static void readXMLOutput() {

		try {
			Feature_vector_file fvf = (Feature_vector_file) ElementState
					.translateFromXML(fvout,// "output.xmlFV.xml",
							TranslationSpace.get("feature_vector_file_scope",
									new Class[] { Feature_vector_file.class,
											DataSet.class, Feature.class,
											V.class, DataSetId.class }));
			for (int i = 0; i < fvf.data_sets.size(); i++) {
				Vector<Double> nums = new Vector<Double>();
				for (int f = 0; f < fvf.data_sets.get(i).features.size(); f++) {

					if (("MFCC Overall Average"
							.equals(fvf.data_sets.get(i).features.get(f).name))) {

						for (int v = 0; v < fvf.data_sets.get(i).features
								.get(f).vs.size(); v++) {
							double n = Double
									.parseDouble(fvf.data_sets.get(i).features
											.get(f).vs.get(v));
							nums.add(n);
						}

					}
				}
				allSounds.sounds.elementAt(mfccIndex).setMFCCs(nums);
				mfccIndex++;
			}

		} catch (Exception e) {
			System.out.println("EXCEPTION IS " + e.toString());
			e.printStackTrace();
		}
		System.out.println(mfccIndex + "done");
	}

	String[] aSrtings = { "", "" };

	// jAudioFeatureExtractor.

	public static Double[] toDoubleArray(double[] input) {
		Double[] ret = new Double[input.length];
		for (int i = 0; i < input.length; i++) {
			ret[i] = new Double(input[i]);
		}
		return ret;
	}

	public static void printV(Double[] v) {
		for (int i = 0; i < v.length; i++) {
			System.out.print(v[i] + "  ");
		}
		System.out.println();
	}

	public static void printV(double[] v) {
		for (int i = 0; i < v.length; i++) {
			System.out.print(v[i] + "  ");
		}
		System.out.println();
	}

	static int depth = 0;

	static void printAllNodeChildren(NodeList nl, String trace) {
		depth++;

		if (nl.getLength() == 0) {
			return;
		}

		System.out.println("trace:" + trace);// At depth "+depth);
		for (int i = 0; i < nl.getLength(); i++) {
			System.out.println("val " + nl.item(i).getNodeValue() + "  name  "
					+ nl.item(i).getNodeName());
		}
		for (int i = 0; i < nl.getLength(); i++) {
			NodeList k = nl.item(i).getChildNodes();

			printAllNodeChildren(k, trace + "->" + (i));
			depth--;
		}
	}

	static File[] listOfFiles;

	public static String[] findFilesInFolder() {
		File folder = new File(folderOfWavFiles);
		listOfFiles = folder.listFiles();
		String[] ret = new String[listOfFiles.length];

		for (int i = 0; i < listOfFiles.length; i++) {
			if (listOfFiles[i].isFile()) {
				System.out.println("File " + listOfFiles[i].getName());
				ret[i] = listOfFiles[i].getName();
			} else if (listOfFiles[i].isDirectory()) {
				System.out.println("Directory " + listOfFiles[i].getName());
			}
		}
		return ret;
	}

	public static void main(String args[]) {

		Scanner in = new Scanner(System.in);
		System.out.println("Width:");
		width = in.nextInt();
		System.out.println("Height:");
		height = in.nextInt();
		System.out.println("Runs:");
		runs = in.nextInt();
		System.out.println("Mfcc Count");
		mfccCount = in.nextInt();

		pwd();
		chooseFolder();
		allSounds = new AllSounds(findFilesInFolder());

		String prefix = folderOfWavFiles;
		prefix = prefix.replace('\\', '.');
		prefix = prefix.replace(':', '.');

		fdout = prefix + "output" + "MFCC"+ mfccCount + ".xmlFD.xml";
		fvout = prefix + "output" + "MFCC"+ mfccCount + ".xmlFV.xml";
		
		boolean fileExists = (new File(fdout)).exists();
		if(fileExists)
		{
			System.out.println("The audio extraction for these settings has already been completed...");
		}
		else
		{
			extractFeatures();
		}
		
		readXMLOutput();
		allSounds.print();

		// NORMALIZE HERE

		Vector<double[]> newMfccs = new Vector<double[]>();

		for (int i = 0; i < allSounds.getMFCCs().size(); i++) {
			newMfccs.add(new double[allSounds.getMFCCs().get(0).length]);
		}

		double weights[] = new double[allSounds.getMFCCs().get(0).length];
		for (int i = 0; i < allSounds.getMFCCs().get(0).length; i++) {
			weights[i] = 1;
		}
		Normalizer.normalize(allSounds.getMFCCs(), weights, newMfccs);
		allSounds.setMFCCs(newMfccs);
		System.out.println("Normalized Sounds:");
		allSounds.print();

		System.out.println("Training network..");

		trainSOM();
		printClusters(clusters);

		UITry3.main(args);
		UITry3.setG(clusters, width, height);// dodododo

	}

	private static void printClusters(Vector<Vector<Sound>> clusters) {
		for (int i = 0; i < clusters.size(); i++) {
			if (clusters.elementAt(i).size() > 0) {
				System.out.println("Now printing out the "
						+ clusters.elementAt(i).size() + " items of cluster "
						+ i);
			}
			for (int k = 0; k < clusters.elementAt(i).size(); k++) {
				System.out.print(clusters.elementAt(i).elementAt(k).filename
						+ " ");
				printV(clusters.elementAt(i).get(k).mfccs);
			}
		}
		System.out.println("Cluster Summary:");
		System.out.println("There are " + clusters.size());
		int numNotThere = 0;
		for (int i = 0; i < clusters.size(); i++) {
			if (clusters.elementAt(i).size() > 0) {
				System.out.println(clusters.get(i).size() + "  ");
			} else {
				numNotThere++;
			}
		}
		System.out.println("There are " + numNotThere + " unpopulated nodes");
	}

	private static void populateClusters(DefaultNetwork network,
			LearningData fileData) {
		for (int n = 0; n < network.getNumbersOfNeurons(); n++) {
			clusters.add(new Vector<Sound>());
		}
		for (int i = 0; i < fileData.getDataSize(); i++) {
			int classNum = 0;
			double currentDistance = Double.MAX_VALUE;
			double[] dataValue = fileData.getData(i);
			for (int n = 0; n < network.getNumbersOfNeurons(); n++) {
				double[] neuronWeight = network.getNeuron(n).getWeight();
				EuclidesMetric m = new EuclidesMetric();
				// MinkowskiMetric m = new MinkowskiMetric(5);

				double dist = m.getDistance(neuronWeight, dataValue);
				if (dist < currentDistance) {
					classNum = n;
					currentDistance = dist;
				}
			}
			clusters.elementAt(classNum).add(allSounds.sounds.get(i));
		}

	}

	private static DefaultNetwork trainSOM() {// (DefaultNetwork network,
												// LearningData fileData) {

		System.out.println("Loading data..");
		// ////LearningData fileData;
		LearningData fileData = new LearningData(allSounds.getMFCCs());
		MatrixTopology topology = new MatrixTopology(width, height);

		double[] maxWeight = new double[(allSounds.getMFCCs().get(0).length)];
		for (int i = 0; i < maxWeight.length; i++) {
			maxWeight[i] = .001;// .05;
		}
		DefaultNetwork network = new DefaultNetwork(
				allSounds.getMFCCs().get(0).length, maxWeight, topology);
		// ConstantFunctionalFactor constantFactor = new
		// ConstantFunctionalFactor(1);//.8

		// ExponentionalFunctionFactor constantFactor = new
		// ExponentionalFunctionFactor(9, 29);

		// LinearFunctionalFactor constantFactor = new
		// LinearFunctionalFactor(.5,200);
		GaussFunctionalFactor constantFactor = new GaussFunctionalFactor();
		double[] params = new double[2];
		params[0] = 2;
		constantFactor.setParameters(params);

		// formerly used Eucidiean
		WTALearningFunction learning = new WTALearningFunction(network, runs,
				new EuclidesMetric(), fileData, constantFactor);
		System.out.println("actually learning");
		learning.learn();
		// network.networkToFile("c:/network_after.txt");
		System.out.println(network);
		System.out.println("projecting");
		populateClusters(network, fileData);
		System.out.println("finished learning");
		return network;
	}
}
